#include <setjmp.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>

#include "config.h"

#define cell fl_cell
#define CELL(x) ((cell)x)
#define CELL_SIZE sizeof(cell)
#define A(x) ((cell *)x)
#define REF(x) *A(x)
#define CA(x) ((char *)x)
#define L(t, n, x) t n = (t)(x)
#define EXP(x, o, y) (CELL(x) o CELL(y))
#define EQ(x, y) EXP(x, ==, y)
#define EQS(x, y) ((*CA(x) == *CA(y)) ? !strcmp(CA(x), CA(y)) : 0)
#define ALIGNMENT(x) ((CELL(x) + (CELL_SIZE - 1)) & (- CELL_SIZE))
#define TRUE CELL(-1)
#define TRUEP(x) EQ(x, TRUE)
#define FALSE CELL(0)
#define FALSEP(x) EQ(x, FALSE)
#define NOT(x) (FALSEP(x) ? TRUE : FALSE)
#define FUN_TYPE(tr, ta) (tr(*)ta)
#define FUN_CALL(tr, f, ta, ...) (FUN_TYPE(tr, ta)f)(__VA_ARGS__)

typedef struct pair {
  cell car;
  cell cdr;
} pair;

#define PAIR(x) ((pair *)x)
#define CAR(x) PAIR(x)->car
#define CDR(x) PAIR(x)->cdr
#define CADR(x) CAR(FL_CDR(x))
#define CARS(x, y) CAR(x) = CELL(y)
#define CDRS(x, y) CDR(x) = CELL(y)
#define CADRS(x, y, z) CARS(x, y); CDRS(x, z)

typedef enum attribute {
  B_NONE = 0,
  B_PRIMITIVE = 1 << 1,
  B_IMMEDIATE = 1 << 2,
  B_CONSTANT = 1 << 3,
} attribute;

typedef struct buffer {
  char *start;
  char *current;
  char *end;
} buffer;

typedef enum port_type {
  T_STRING,
  T_FILE,
} port_type;

typedef struct file_input {
  int fd;
} file_input;

typedef struct input {
  port_type type;
  buffer buffer;
  union {
    file_input file;
  };
} input;

typedef struct file_output {
  int fd;
} file_output;

typedef struct output {
  port_type type;
  buffer buffer;
  union {
    file_output file;
  };
} output;

/* Word

 xt = [ attribute previous name code ...] 
                            ^xt 
*/
#define XT_NAME_OFFSET -CELL
#define XT_PREVIOUS_OFFSET -(CELL * 2)
#define XT_ATTIRBUTE_OFFSET -(CELL * 3)
#define XT_HEADER_SIZE (CELL * 3)
#define XT_REF(x, n) A(EXP(x, +, XT_##_OFFSET))
#define XT_VAL(x, n) REF(XT_REF(x, n))

/* Util */

static void _abort(const char *m, ...)
{
  va_list args;
  va_start(args, m);
  vfprintf(stderr, m, args);
  va_end(args);
  fputc('\n', stderr);
  fflush(stderr);
  _exit(1);
}

#define ABORT(m) _abort("%s - %d: " m, __FILE__, __LINE__)
#define ABORTF(m, ...) _abort("%s - %d: " m, __FILE__, __LINE__, __VA_ARGS__)

/* Exception */

#define EXCEPTION_MESSAGE(x) exception_message[x]
#define EXCEPTION(x) E_##x

typedef enum ExceptionCode {
#define X(a, b, x) EXCEPTION(a) = b,
#include "tmp/exceptions.h"
#undef X
} ExceptionCode;

const char *exception_message[] = {
#define X(x, n, s) s,
#include "tmp/exceptions.h"
#undef X
};

/* Symbol */

static pair *symbol_table[SYMBOL_TABLE_SIZE];

static void symbol_startup()
{
  int i = SYMBOL_TABLE_LENGTH;
  while (i) symbol_table[--i] = NULL;
}

/* Main */

int fl_argc;
char **fl_argv;

fl_image *fl_image_init(void *memory, int size)
{
  static int _one = 1;
  if (_one) {
    _one = 0;
  }
  fl_image *i = (fl_image *)memory;
  i->here = i->space;
  i->latest = NULL;
  i->size = size;
  return i;
}

fl_engine *fl_engine_new(void *memory, int ds, int rs)
{
  static int id = 0;
  fl_engine *e = (fl_engine *)memory;
  e->id = id++;
  e->ip = NULL;
  e->cb = NULL;
  e->ds.base = A(EXP(memory, +, sizeof(fl_engine)));
  e->ds.top = A(EXP(e->ds.base, +, ds));
  e->ds.current = e->ds.top;
  e->rs.base = A(EXP(e->ds.top, +, CELL_SIZE));
  e->rs.top = A(EXP(e->rs.base, +, rs));
  e->rs.current = e->rs.top;
  return e;
}

void fl_eval(fl_image *image, fl_engine *engine, const char *src)
{
  fl_start(image, engine);
}

int fl_start(fl_image *I, fl_engine *E, cell xt[])
{
  register cell *IP = xt;
  register cell *SP = E->ds.current;
  register cell *RP = E->rs.current;
  register cell R1, R2, R3;

  #define SWITCH goto **IP++;
  #define NEXT goto **IP++
  #define JUMP(x) goto *(void *)(x)
  #define IP_SET(x) IP = A(x);
  #define IP_INC(n) IP += n
  #define S(i) SP[(i)]
  #define PUSH(s, b) R3S(b); *(--s) = R3
  #define POP(s) CELL(*s++)
  #define DROP(s) (++s)
  #define DUP(x) PUSH(x, *x)
  #define TOS *SP
  #define TOS_SET(s) TOS = CELL(s)
  #define R1S(x) R1 = CELL(x)
  #define R2S(x) R2 = CELL(x)
  #define R3S(x) R3 = CELL(x)
  #define THROW(n) PUSH(SP, EXCEPTION_MESSAGE(n)); goto throw
  /* Image */
  #define HERE I->here
  #define LATEST I->latest
  #define HERE_SET(x) HERE = CELL(x);
  #define HERE_INC(x) HERE_SET(EXP(HERE, +, x))
  #define ALIGN() HERE_SET(ALIGNMENT(HERE))
  #define COMPILE(x) *A(HERE) = CELL(x); HERE_INC(CELL_SIZE)
  #define COMPILEC(x) *CA(HERE) = ((char)x); HERE_INC(sizeof(char))
  #define NEW(t, n) L(t *, n, HERE); HERE_INC(sizeof(t))
  #define ALLOT(n) HERE; HERE_INC(n)
  
  NEXT;
  #define ENGINE_CODE_LOAD
  #include "tmp/engine.h"
  
 bye:
  int r = POP(SP);
  E->ip = IP;
  E->ds.current = SP;
  E->rs.current = RP;
  E->state = FALSE;
  return r;
}
